<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>無人機垂直起降位置控制模擬 (純P/PD)</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@1.4.0/dist/chartjs-plugin-annotation.min.js"></script>
    <style>
        /* 自定義樣式 */
        body {
            font-family: 'Inter', sans-serif;
            overscroll-behavior: none; /* 防止滾動鏈 */
        }
        /* 調整滑塊標籤在小屏幕上的樣式 */
        .slider-label {
            min-width: 90px; /* 略微減小最小寬度 */
            text-align: right;
            margin-right: 8px; /* 略微減小間距 */
            font-size: 0.875rem; /* 略微減小字體 */
        }
        .slider-value {
            min-width: 45px; /* 略微減小最小寬度 */
            text-align: left;
            font-size: 0.875rem; /* 略微減小字體 */
        }
         /* 調整指標標籤在小屏幕上的樣式 */
        .metrics-label {
             min-width: 100px; /* 略微減小最小寬度 */
             text-align: right;
             margin-right: 8px;
             font-weight: 500;
             color: #4b5563;
             font-size: 0.875rem;
        }
        .metrics-value {
            min-width: 55px;
            text-align: left;
            font-family: monospace;
            color: #1d4ed8;
            font-size: 0.875rem;
        }
        #drone {
            width: 40px; /* 減小無人機大小 */
            height: 16px;
            background-color: #3b82f6;
            border-radius: 4px;
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: 10px;
            transition: bottom 0.05s linear;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        #drone::before, #drone::after {
            content: '';
            position: absolute;
            width: 55px; /* 減小螺旋槳大小 */
            height: 3px;
            background-color: #9ca3af;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            border-radius: 1.5px;
        }
        #drone::after {
            transform: translate(-50%, -50%) rotate(90deg);
        }
        #visualization-area {
            position: relative;
            height: 250px; /* 減小可視化區域高度 */
            background-color: #f3f4f6;
            border-radius: 8px;
            overflow: hidden;
            border: 1px solid #e5e7eb;
        }
        #target-line {
            position: absolute;
            width: 100%;
            height: 2px;
            background-color: #ef4444;
            left: 0;
            bottom: 50%;
            z-index: 5;
            transition: bottom 0.2s ease-in-out;
        }
        .control-panel label, .metrics-display div {
            display: flex;
            align-items: center;
            margin-bottom: 10px; /* 增加垂直間距 */
        }

        input[type="range"] {
            -webkit-appearance: none;
            appearance: none;
            width: 100%;
            height: 10px; /* 增加軌道高度以便觸摸 */
            background: #d1d5db;
            border-radius: 5px;
            outline: none;
            opacity: 0.7;
            transition: opacity .2s;
            cursor: pointer;
        }
        input[type="range"]:hover {
            opacity: 1;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 24px; /* 增大滑塊大小以便觸摸 */
            height: 24px;
            background: #3b82f6;
            border-radius: 50%;
            cursor: pointer;
        }
        input[type="range"]::-moz-range-thumb {
            width: 24px;
            height: 24px;
            background: #3b82f6;
            border-radius: 50%;
            cursor: pointer;
            border: none;
        }
        button {
            transition: all 0.3s ease;
             padding: 10px 16px; /* 增大按鈕內邊距 */
             font-size: 0.9rem; /* 調整按鈕字體大小 */
        }
        button:hover {
            transform: translateY(-1px); /* 減小懸停效果 */
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
        }
        .radio-label {
            display: inline-flex;
            align-items: center;
            cursor: pointer;
            margin-right: 10px; /* 減小單選按鈕間距 */
            padding: 6px 12px; /* 調整單選按鈕內邊距 */
            border: 1px solid #d1d5db;
            border-radius: 6px;
            transition: background-color 0.3s ease, border-color 0.3s ease;
             font-size: 0.875rem;
        }
        .radio-label input[type="radio"] {
            display: none;
        }
        .radio-label span {
            margin-left: 4px;
        }
        .radio-label:has(input[type="radio"]:checked) {
            background-color: #bfdbfe;
            border-color: #3b82f6;
        }
        /* 圖表容器 */
        #chart-container {
            position: relative;
            height: 350px; /* 為手機設置一個固定高度 */
            width: 100%; /* 確保寬度充滿 */
        }
        /* 響應式標題 */
        h1 {
            font-size: 1.5rem; /* 基礎字體大小 */
        }
        h2 {
             font-size: 1.125rem; /* 基礎字體大小 */
        }
        /* 在中等屏幕及以上恢復桌面端字體大小 */
        @media (min-width: 768px) {
            h1 { font-size: 1.875rem; } /* text-3xl */
            h2 { font-size: 1.25rem; } /* text-xl */
            .slider-label, .slider-value, .metrics-label, .metrics-value, .radio-label {
                 font-size: 1rem; /* 恢復默認字體大小 */
            }
            #chart-container {
                 min-height: 500px; /* 恢復桌面端最小高度 */
                 height: auto; /* 允許自動高度 */
            }
             #visualization-area {
                 height: 300px; /* 恢復桌面端可視化高度 */
             }
        }

    </style>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body class="bg-gray-100 p-4"> <div class="max-w-4xl mx-auto bg-white p-4 md:p-6 rounded-lg shadow-lg"> <h1 class="font-bold text-center text-gray-800 mb-3">無人機垂直位置控制模擬</h1>
        <p class="text-center text-gray-600 text-sm mb-5">體驗純 P/PD 控制（無重力補償）的影響。</p>

        <div class="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6">

            <div class="space-y-4 md:space-y-6">
                <div>
                    <h2 class="font-semibold text-gray-700 mb-2">無人機狀態</h2>
                    <div id="visualization-area">
                        <div id="target-line" title="目標高度"></div>
                        <div id="drone"></div>
                    </div>
                     <p class="text-xs text-gray-500 mt-1 text-center">↑ 當前高度: <span id="current-height-display">10.0</span> m</p>
                </div>

                <div class="bg-gray-50 p-4 rounded-lg border border-gray-200 control-panel">
                    <h2 class="font-semibold text-gray-700 mb-4">控制參數調整</h2>
                    <label for="targetHeight">
                        <span class="slider-label font-medium text-gray-600">目標 (m):</span> <input type="range" id="targetHeight" min="10" max="90" value="50" step="1" class="flex-grow mx-1"> <span class="slider-value font-mono text-blue-600" id="targetHeightValue">50</span>
                    </label>
                    <label for="kp">
                        <span class="slider-label font-medium text-gray-600">Kp:</span> <input type="range" id="kp" min="0.01" max="5" value="1.5" step="0.01" class="flex-grow mx-1">
                        <span class="slider-value font-mono text-blue-600" id="kpValue">1.50</span>
                    </label>
                    <label for="kd">
                        <span class="slider-label font-medium text-gray-600">Kd:</span> <input type="range" id="kd" min="0" max="5" value="1.0" step="0.1" class="flex-grow mx-1">
                        <span class="slider-value font-mono text-blue-600" id="kdValue">1.0</span>
                    </label>
                     <div class="mb-3"> <span class="font-medium text-gray-600 mr-2 text-sm">模式:</span> <label class="radio-label">
                               <input type="radio" name="controlMode" value="P" checked>
                               <span>P</span>
                          </label>
                          <label class="radio-label">
                               <input type="radio" name="controlMode" value="PD">
                               <span>PD</span>
                          </label>
                     </div>
                    <div class="flex flex-wrap justify-center gap-2 mt-3"> <button id="startButton" class="bg-blue-500 text-white font-semibold rounded-lg shadow hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">
                            啟動
                        </button>
                        <button id="resetButton" class="bg-gray-500 text-white font-semibold rounded-lg shadow hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-75">
                            重置
                        </button>
                         <button id="clearHistoryButton" class="bg-red-500 text-white font-semibold rounded-lg shadow hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-opacity-75">
                            清除
                        </button>
                    </div>
                </div>

                 <div class="bg-gray-50 p-4 rounded-lg border border-gray-200 metrics-display">
                     <h2 class="font-semibold text-gray-700 mb-3">性能指標</h2> <div>
                         <span class="metrics-label">超調 (%):</span>
                         <span class="metrics-value" id="overshootValue">-</span>
                     </div>
                     <div>
                         <span class="metrics-label">上升 (s):</span> <span class="metrics-value" id="riseTimeValue">-</span>
                     </div>
                      <div>
                         <span class="metrics-label">調節 (s):</span> <span class="metrics-value" id="settlingTimeValue">-</span>
                     </div>
                     <div>
                         <span class="metrics-label">穩態 (m):</span> <span class="metrics-value" id="steadyStateValue">-</span>
                     </div>
                     <div>
                         <span class="metrics-label">誤差 (%):</span> <span class="metrics-value" id="steadyStateErrorValue">-</span>
                     </div>
                 </div>
            </div>

            <div class="bg-gray-50 p-3 md:p-4 rounded-lg border border-gray-200 flex flex-col"> <h2 class="font-semibold text-gray-700 mb-2 text-center">動態響應曲線</h2>
                 <div id="chart-container" class="flex-grow">
                     <canvas id="responseChart"></canvas>
                 </div>
            </div>
        </div>
    </div>

    <script>
        // --- DOM 元素 ---
        const targetHeightSlider = document.getElementById('targetHeight');
        const kpSlider = document.getElementById('kp');
        const kdSlider = document.getElementById('kd');
        const targetHeightValueSpan = document.getElementById('targetHeightValue');
        const kpValueSpan = document.getElementById('kpValue');
        const kdValueSpan = document.getElementById('kdValue');
        const startButton = document.getElementById('startButton');
        const resetButton = document.getElementById('resetButton');
        const clearHistoryButton = document.getElementById('clearHistoryButton');
        const droneElement = document.getElementById('drone');
        const targetLineElement = document.getElementById('target-line');
        const visualizationArea = document.getElementById('visualization-area');
        const currentHeightDisplay = document.getElementById('current-height-display');
        const controlModeRadios = document.querySelectorAll('input[name="controlMode"]');
        const chartCanvas = document.getElementById('responseChart');
        // 指标显示元素
        const overshootValueSpan = document.getElementById('overshootValue');
        const riseTimeValueSpan = document.getElementById('riseTimeValue');
        const settlingTimeValueSpan = document.getElementById('settlingTimeValue');
        const steadyStateValueSpan = document.getElementById('steadyStateValue');
        const steadyStateErrorValueSpan = document.getElementById('steadyStateErrorValue');


        // --- 模拟参数 ---
        const dt = 0.02; // 模拟时间步长 (秒)
        const gravity = 9.81; // 重力加速度 (m/s^2)
        const mass = 1.0; // 无人机质量 (kg)
        const maxForce = 25; // 最大控制力
        const minForce = 0;   // 最小控制力
        const dampingFactor = 0.5; // 自然阻尼/空气阻力系数
        const SETTLING_THRESHOLD_PERCENT = 0.02; // 2% 调节时间阈值

        // --- 模拟状态 ---
        let position = 10.0; // 初始位置 (m)
        let velocity = 0.0; // 初始速度 (m/s)
        let time = 0.0; // 模拟时间 (s) - 重置后会累积
        let targetHeight = parseFloat(targetHeightSlider.value); // 目标高度 (m)
        let Kp = parseFloat(kpSlider.value); // 比例增益
        let Kd = parseFloat(kdSlider.value); // 微分增益
        let controlMode = 'P'; // 当前控制模式 ('P' or 'PD')
        let simulationRunning = false;
        let animationFrameId = null;
        let resetAnnotations = []; // 存储重置时间的标注

        // --- 当前运行数据 (用于指标计算) ---
        let currentRunStartTime = 0;
        let currentRunInitialPosition = position;
        let currentRunTargetHeight = targetHeight;
        let currentRunTimeData = [];
        let currentRunPositionData = [];


        // --- Chart.js 设置 ---
        let chart;
        let chartLabels = []; // 存储所有历史时间点
        let actualHeightData = []; // 存储所有历史实际高度
        let targetHeightData = []; // 存储所有历史目标高度

        let chartData = {
            labels: chartLabels,
            datasets: [
                {
                    label: '实际高度 (m)',
                    data: actualHeightData,
                    borderColor: 'rgb(59, 130, 246)', // 蓝色
                    borderWidth: 1.5, // 减小线宽
                    tension: 0.1,
                    pointRadius: 0 // 不显示点
                },
                {
                    label: '目标高度 (m)',
                    data: targetHeightData,
                    borderColor: 'rgb(239, 68, 68)', // 红色
                    borderWidth: 1.5, // 减小线宽
                    borderDash: [4, 4], // 调整虚线样式
                    pointRadius: 0, // 不显示点
                    stepped: true,
                }
            ]
        };

        function initializeChart() {
            const ctx = chartCanvas.getContext('2d');
             if (chart) {
                 chart.destroy();
             }
            try {
                 chart = new Chart(ctx, {
                     type: 'line',
                     data: chartData,
                     options: {
                         responsive: true,
                         maintainAspectRatio: false, // 重要：允许图表填充容器高度
                         animation: false,
                         scales: {
                             x: {
                                 type: 'linear',
                                 title: { display: true, text: '时间 (s)', font: { size: 10 } }, // 减小坐标轴标题字号
                                 ticks: { font: { size: 9 } } // 减小刻度字号
                             },
                             y: {
                                 title: { display: true, text: '高度 (m)', font: { size: 10 } }, // 减小坐标轴标题字号
                                 suggestedMin: 0,
                                 suggestedMax: 100,
                                 ticks: { font: { size: 9 } } // 减小刻度字号
                             }
                         },
                         plugins: {
                             legend: {
                                 position: 'top',
                                 labels: { font: { size: 10 } } // 减小图例字号
                             },
                             tooltip: {
                                 mode: 'index',
                                 intersect: false,
                                 titleFont: { size: 10 }, // 减小提示框字号
                                 bodyFont: { size: 9 }
                             },
                             annotation: {
                                   drawTime: 'afterDatasetsDraw',
                                   annotations: {}
                              }
                         }
                     }
                 });
                 console.log("图表初始化成功。");
             } catch (error) {
                  console.error("初始化图表时发生错误:", error);
             }
        }

        // --- 模拟逻辑 (与之前相同) ---
        function simulationStep() {
            if (!simulationRunning || !chart) return;

            targetHeight = parseFloat(targetHeightSlider.value);
            Kp = parseFloat(kpSlider.value);
            Kd = parseFloat(kdSlider.value);
            controlMode = document.querySelector('input[name="controlMode"]:checked').value;

            const error = targetHeight - position;
            const errorDerivative = -velocity;
            let controlForce = 0;
            if (controlMode === 'P') {
                controlForce = Kp * error;
            } else {
                controlForce = Kp * error + Kd * errorDerivative;
            }
            controlForce -= dampingFactor * velocity;
            controlForce = Math.max(minForce, Math.min(maxForce, controlForce));

            const netForce = controlForce - (mass * gravity);
            const acceleration = netForce / mass;
            velocity += acceleration * dt;
            position += velocity * dt;

             if (position < 0) {
                 position = 0;
                 if (velocity < 0) velocity *= -0.3;
             }
             const maxHeight = 100;
             if (position > maxHeight) {
                 position = maxHeight;
                 if (velocity > 0) velocity = 0;
             }

            time += dt;
            currentRunTimeData.push(time);
            currentRunPositionData.push(position);

            updateVisualization();
            updateChartData();
            animationFrameId = requestAnimationFrame(simulationStep);
        }

        // --- 更新可视化元素 (与之前相同) ---
        function updateVisualization() {
            const visHeight = visualizationArea.clientHeight;
            const bottomOffset = 10;
            const topOffset = 10;
            const drawableHeight = visHeight - bottomOffset - topOffset;
            const clampedPosition = Math.max(0, Math.min(100, position));
            const droneBottomPx = (clampedPosition / 100) * drawableHeight + bottomOffset;
            droneElement.style.bottom = `${droneBottomPx}px`;

            const targetBottomPx = (targetHeight / 100) * drawableHeight + bottomOffset;
            targetLineElement.style.bottom = `${targetBottomPx}px`;

            currentHeightDisplay.textContent = position.toFixed(1);
        }

        // --- 更新图表数据 (与之前相同) ---
        function updateChartData() {
            if (!chart) return;
            const currentTimeStr = time.toFixed(2);
            chartLabels.push(currentTimeStr);
            actualHeightData.push(position.toFixed(2));
            targetHeightData.push(targetHeight.toFixed(2));
            chart.update('none');
        }

        // --- 计算并显示性能指标 (与之前相同, 包含错误处理) ---
        function calculateAndDisplayMetrics() {
            if (currentRunPositionData.length < 10) {
                 displayMetrics('-', '-', '-', '-', '-');
                 console.log("数据点不足，无法计算指标。");
                 return;
            }

            const runTimes = currentRunTimeData;
            const runPositions = currentRunPositionData;
            const nPoints = runPositions.length;
            const initialPos = currentRunInitialPosition;
            const targetPos = currentRunTargetHeight;
            const startTime = currentRunStartTime;

            // 稳态值
            const steadyStatePoints = Math.min(50, Math.max(10, Math.floor(nPoints * 0.2)));
            let sum = 0;
            if (nPoints - steadyStatePoints < 0) { // 检查计算出的起始索引是否有效
                 console.warn(`计算稳态值时点数不足: nPoints=${nPoints}, steadyStatePoints=${steadyStatePoints}`);
                 displayMetrics('错误', '错误', '错误', '错误', '错误');
                 return;
             }
            for (let i = nPoints - steadyStatePoints; i < nPoints; i++) {
                 sum += runPositions[i];
            }
            const steadyStateValue = steadyStatePoints > 0 ? sum / steadyStatePoints : runPositions[nPoints - 1];


            // 稳态误差
            let steadyStateErrorPercent = 0;
            if (Math.abs(targetPos) > 1e-6) {
                 steadyStateErrorPercent = ((steadyStateValue - targetPos) / targetPos) * 100;
            }

            // 超调量
            const maxPos = Math.max(...runPositions);
            let overshootPercent = 0;
            const range = targetPos - initialPos;
            if (range > 1e-6 && maxPos > targetPos) {
                 overshootPercent = ((maxPos - targetPos) / range) * 100;
            } else if (range < -1e-6 && maxPos < targetPos) {
                 overshootPercent = 0;
            }


            // 上升时间 (10% to 90% of steady-state relative to initial)
            const stepChange = steadyStateValue - initialPos;
            let riseTime = -1;
            if (Math.abs(stepChange) > 1e-6) {
                const tenPercentTarget = initialPos + 0.1 * stepChange;
                const ninetyPercentTarget = initialPos + 0.9 * stepChange;
                let t10 = -1, t90 = -1;

                const rising = stepChange > 0;
                for (let i = 0; i < nPoints; i++) {
                    const currentPos = runPositions[i];
                    if (t10 < 0 && (rising ? currentPos >= tenPercentTarget : currentPos <= tenPercentTarget)) {
                        t10 = runTimes[i];
                    }
                    if (t90 < 0 && (rising ? currentPos >= ninetyPercentTarget : currentPos <= ninetyPercentTarget)) {
                        t90 = runTimes[i];
                        break;
                    }
                }
                 riseTime = (t10 >= 0 && t90 >= 0) ? (t90 - t10) : -1;
            }


             // 调节时间 (Settling Time, ±2% of steady-state value)
             const upperBound = steadyStateValue * (1 + SETTLING_THRESHOLD_PERCENT);
             const lowerBound = steadyStateValue * (1 - SETTLING_THRESHOLD_PERCENT);
             let settlingTime = -1;
             let lastOutOfBoundIndex = -1;
             for (let i = 0; i < nPoints; i++) {
                 if (runPositions[i] > upperBound || runPositions[i] < lowerBound) {
                     lastOutOfBoundIndex = i;
                 }
             }

             if (lastOutOfBoundIndex === -1 && nPoints > 0) {
                 settlingTime = runTimes[0] - startTime;
             } else if (lastOutOfBoundIndex < nPoints - 1) {
                 settlingTime = runTimes[lastOutOfBoundIndex + 1] - startTime;
             } else {
                 settlingTime = -1;
             }


            displayMetrics(
                overshootPercent,
                riseTime,
                settlingTime,
                steadyStateValue,
                steadyStateErrorPercent
            );

            console.log("性能指标已计算:", {
                 overshoot: overshootPercent.toFixed(2) + '%',
                 riseTime: riseTime >= 0 ? riseTime.toFixed(2) + 's' : 'N/A',
                 settlingTime: settlingTime >= 0 ? settlingTime.toFixed(2) + 's' : 'N/A',
                 steadyStateValue: steadyStateValue.toFixed(2) + 'm',
                 steadyStateError: steadyStateErrorPercent.toFixed(2) + '%'
             });
        }

        // --- 更新指标显示函数 (与之前相同) ---
        function displayMetrics(overshoot, rise, settling, steadyVal, steadyErr) {
            overshootValueSpan.textContent = typeof overshoot === 'number' ? overshoot.toFixed(2) : '-';
            riseTimeValueSpan.textContent = typeof rise === 'number' && rise >= 0 ? rise.toFixed(2) : '-';
            settlingTimeValueSpan.textContent = typeof settling === 'number' && settling >= 0 ? settling.toFixed(2) : '-';
            steadyStateValueSpan.textContent = typeof steadyVal === 'number' ? steadyVal.toFixed(2) : '-';
            steadyStateErrorValueSpan.textContent = typeof steadyErr === 'number' ? steadyErr.toFixed(2) : '-';
        }

        // --- 重置函数 (与之前相同) ---
        function resetSimulation(keepHistory = true) {
            if (simulationRunning && keepHistory) {
                 calculateAndDisplayMetrics();
            }
            simulationRunning = false;
            if (animationFrameId) {
                cancelAnimationFrame(animationFrameId);
                animationFrameId = null;
            }

             if (keepHistory && time > 0.01 && chart && chart.options?.plugins?.annotation) {
                 const resetTimeStr = time.toFixed(2);
                 const annotationId = 'resetLine' + resetAnnotations.length;
                 if (!chart.options.plugins.annotation.annotations) {
                     chart.options.plugins.annotation.annotations = {};
                 }
                 chart.options.plugins.annotation.annotations[annotationId] = {
                       type: 'line', scaleID: 'x', value: resetTimeStr,
                       borderColor: 'rgba(107, 114, 128, 0.5)', borderWidth: 1, borderDash: [6, 6],
                       label: { content: '重置', enabled: true, position: 'start', backgroundColor: 'rgba(107, 114, 128, 0.5)', font: { size: 10 }, yAdjust: -10 }
                 };
                 resetAnnotations.push(annotationId);
                 console.log(`已在时间点添加标注: ${resetTimeStr}`);
             }

            position = 10.0;
            velocity = 0.0;
            currentRunTimeData = [];
            currentRunPositionData = [];

            if (!keepHistory) {
                time = 0.0;
                chartLabels.length = 0;
                actualHeightData.length = 0;
                targetHeightData.length = 0;
                resetAnnotations.length = 0;
                if (chart && chart.options?.plugins?.annotation) {
                     chart.options.plugins.annotation.annotations = {};
                }
                chartLabels.push(time.toFixed(2));
                actualHeightData.push(position.toFixed(2));
                targetHeightData.push(parseFloat(targetHeightSlider.value).toFixed(2));
                displayMetrics('-', '-', '-', '-', '-');
            } else {
                targetHeightData.length = actualHeightData.length;
                 for (let i = 0; i < actualHeightData.length; i++) {
                     if (targetHeightData[i] === undefined) {
                          targetHeightData[i] = null;
                     }
                 }
            }

            targetHeight = parseFloat(targetHeightSlider.value);
            Kp = parseFloat(kpSlider.value);
            Kd = parseFloat(kdSlider.value);
            controlMode = document.querySelector('input[name="controlMode"]:checked').value;

            updateVisualization();

            if (chart) {
                 chart.update();
            } else {
                 initializeChart();
            }

            startButton.textContent = '启动'; // 恢复按钮文本
            startButton.disabled = false;
            startButton.classList.remove('bg-yellow-500', 'hover:bg-yellow-600', 'bg-green-500', 'hover:bg-green-600');
            startButton.classList.add('bg-blue-500', 'hover:bg-blue-600');

            console.log(`模拟状态已重置 (${keepHistory ? '保留历史记录' : '清除历史记录'})`);
        }

        // --- 事件监听器 (与之前相同) ---
        targetHeightSlider.addEventListener('input', (e) => {
            targetHeightValueSpan.textContent = e.target.value;
            targetHeight = parseFloat(e.target.value);
            updateVisualization();
        });

        kpSlider.addEventListener('input', (e) => {
            kpValueSpan.textContent = parseFloat(e.target.value).toFixed(2);
            Kp = parseFloat(e.target.value);
        });

        kdSlider.addEventListener('input', (e) => {
            kdValueSpan.textContent = parseFloat(e.target.value).toFixed(1);
            Kd = parseFloat(e.target.value);
        });

         controlModeRadios.forEach(radio => {
             radio.addEventListener('change', (e) => {
                 controlMode = e.target.value;
                 console.log("控制模式已变更为:", controlMode);
             });
         });


        startButton.addEventListener('click', () => {
            if (!chart) {
                console.error("图表未初始化。无法启动模拟。");
                initializeChart();
                if (!chart) return;
            }

            if (!simulationRunning) {
                simulationRunning = true;
                currentRunStartTime = time;
                currentRunInitialPosition = position;
                currentRunTargetHeight = parseFloat(targetHeightSlider.value);
                currentRunTimeData = [time];
                currentRunPositionData = [position];

                startButton.textContent = '暂停'; // 更新按钮文本
                startButton.classList.remove('bg-blue-500', 'hover:bg-blue-600', 'bg-green-500', 'hover:bg-green-600');
                startButton.classList.add('bg-yellow-500', 'hover:bg-yellow-600');

                 if (targetHeightData.length < actualHeightData.length) {
                     const gap = actualHeightData.length - targetHeightData.length;
                      for(let i=0; i<gap; i++) { targetHeightData.push(null); }
                 }
                 if (chartLabels.length > 0 && targetHeightData.length === chartLabels.length) {
                      targetHeightData[targetHeightData.length - 1] = targetHeight.toFixed(2);
                 } else if (chartLabels.length > targetHeightData.length) {
                       targetHeightData.push(targetHeight.toFixed(2));
                 }

                animationFrameId = requestAnimationFrame(simulationStep);
                console.log("模拟已启动/恢复");
            } else {
                simulationRunning = false;
                if (animationFrameId) {
                    cancelAnimationFrame(animationFrameId);
                    animationFrameId = null;
                }
                calculateAndDisplayMetrics();
                startButton.textContent = '继续'; // 更新按钮文本
                 startButton.classList.remove('bg-yellow-500', 'hover:bg-yellow-600');
                 startButton.classList.add('bg-green-500', 'hover:bg-green-600');
                console.log("模拟已暂停");
            }
        });

        resetButton.addEventListener('click', () => {
             if (simulationRunning) { calculateAndDisplayMetrics(); }
             resetSimulation(true);
        });

        clearHistoryButton.addEventListener('click', () => {
            resetSimulation(false);
        });


        // --- 初始设置 (与之前相同) ---
        window.addEventListener('load', () => {
            targetHeightValueSpan.textContent = targetHeightSlider.value;
            kpValueSpan.textContent = parseFloat(kpSlider.value).toFixed(2);
            kdValueSpan.textContent = parseFloat(kdSlider.value).toFixed(1);
            currentHeightDisplay.textContent = position.toFixed(1);

            initializeChart();
            updateVisualization();

             if (chart) {
                 chartLabels.push(time.toFixed(2));
                 actualHeightData.push(position.toFixed(2));
                 targetHeightData.push(targetHeight.toFixed(2));
                 chart.update();
             }
             displayMetrics('-', '-', '-', '-', '-');
        });

         // 处理窗口大小调整 (与之前相同) ---
         let resizeTimeout;
         window.addEventListener('resize', () => {
              clearTimeout(resizeTimeout);
              resizeTimeout = setTimeout(() => {
                   if (chart) {
                       chart.resize();
                   }
                   updateVisualization();
              }, 250);
         });

    </script>

</body>
</html>
